// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/proxy/proxy_config_service_linux.h"

#include <map>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service_common_unittest.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

namespace net {
namespace {

    // Set of values for all environment variables that we might
    // query. NULL represents an unset variable.
    struct EnvVarValues {
        // The strange capitalization is so that the field matches the
        // environment variable name exactly.
        const char* DESKTOP_SESSION;
        const char* HOME;
        const char* KDEHOME;
        const char* KDE_SESSION_VERSION;
        const char* XDG_CURRENT_DESKTOP;
        const char* auto_proxy;
        const char* all_proxy;
        const char* http_proxy;
        const char* https_proxy;
        const char* ftp_proxy;
        const char* SOCKS_SERVER;
        const char* SOCKS_VERSION;
        const char* no_proxy;
    };

// Undo macro pollution from GDK includes (from message_loop.h).
#undef TRUE
#undef FALSE

    // So as to distinguish between an unset gconf boolean variable and
    // one that is false.
    enum BoolSettingValue {
        UNSET = 0,
        TRUE,
        FALSE
    };

    // Set of values for all gconf settings that we might query.
    struct GConfValues {
        // strings
        const char* mode;
        const char* autoconfig_url;
        const char* http_host;
        const char* secure_host;
        const char* ftp_host;
        const char* socks_host;
        // integers
        int http_port;
        int secure_port;
        int ftp_port;
        int socks_port;
        // booleans
        BoolSettingValue use_proxy;
        BoolSettingValue same_proxy;
        BoolSettingValue use_auth;
        // string list
        std::vector<std::string> ignore_hosts;
    };

    // Mapping from a setting name to the location of the corresponding
    // value (inside a EnvVarValues or GConfValues struct).
    template <typename key_type, typename value_type>
    struct SettingsTable {
        typedef std::map<key_type, value_type*> map_type;

        // Gets the value from its location
        value_type Get(key_type key)
        {
            auto it = settings.find(key);
            // In case there's a typo or the unittest becomes out of sync.
            CHECK(it != settings.end()) << "key " << key << " not found";
            value_type* value_ptr = it->second;
            return *value_ptr;
        }

        map_type settings;
    };

    class MockEnvironment : public base::Environment {
    public:
        MockEnvironment()
        {
#define ENTRY(x) table_[#x] = &values.x
            ENTRY(DESKTOP_SESSION);
            ENTRY(HOME);
            ENTRY(KDEHOME);
            ENTRY(KDE_SESSION_VERSION);
            ENTRY(XDG_CURRENT_DESKTOP);
            ENTRY(auto_proxy);
            ENTRY(all_proxy);
            ENTRY(http_proxy);
            ENTRY(https_proxy);
            ENTRY(ftp_proxy);
            ENTRY(no_proxy);
            ENTRY(SOCKS_SERVER);
            ENTRY(SOCKS_VERSION);
#undef ENTRY
            Reset();
        }

        // Zeroes all environment values.
        void Reset()
        {
            EnvVarValues zero_values = { 0 };
            values = zero_values;
        }

        // Begin base::Environment implementation.
        bool GetVar(base::StringPiece variable_name, std::string* result) override
        {
            auto it = table_.find(variable_name);
            if (it == table_.end() || !*it->second)
                return false;

            // Note that the variable may be defined but empty.
            *result = *(it->second);
            return true;
        }

        bool SetVar(base::StringPiece variable_name,
            const std::string& new_value) override
        {
            ADD_FAILURE();
            return false;
        }

        bool UnSetVar(base::StringPiece variable_name) override
        {
            ADD_FAILURE();
            return false;
        }
        // End base::Environment implementation.

        // Intentionally public, for convenience when setting up a test.
        EnvVarValues values;

    private:
        std::map<base::StringPiece, const char**> table_;
    };

    class MockSettingGetter
        : public ProxyConfigServiceLinux::SettingGetter {
    public:
        typedef ProxyConfigServiceLinux::SettingGetter SettingGetter;
        MockSettingGetter()
        {
#define ENTRY(key, field) \
    strings_table.settings[SettingGetter::key] = &values.field
            ENTRY(PROXY_MODE, mode);
            ENTRY(PROXY_AUTOCONF_URL, autoconfig_url);
            ENTRY(PROXY_HTTP_HOST, http_host);
            ENTRY(PROXY_HTTPS_HOST, secure_host);
            ENTRY(PROXY_FTP_HOST, ftp_host);
            ENTRY(PROXY_SOCKS_HOST, socks_host);
#undef ENTRY
#define ENTRY(key, field) \
    ints_table.settings[SettingGetter::key] = &values.field
            ENTRY(PROXY_HTTP_PORT, http_port);
            ENTRY(PROXY_HTTPS_PORT, secure_port);
            ENTRY(PROXY_FTP_PORT, ftp_port);
            ENTRY(PROXY_SOCKS_PORT, socks_port);
#undef ENTRY
#define ENTRY(key, field) \
    bools_table.settings[SettingGetter::key] = &values.field
            ENTRY(PROXY_USE_HTTP_PROXY, use_proxy);
            ENTRY(PROXY_USE_SAME_PROXY, same_proxy);
            ENTRY(PROXY_USE_AUTHENTICATION, use_auth);
#undef ENTRY
            string_lists_table.settings[SettingGetter::PROXY_IGNORE_HOSTS] = &values.ignore_hosts;
            Reset();
        }

        // Zeros all environment values.
        void Reset()
        {
            GConfValues zero_values = { 0 };
            values = zero_values;
        }

        bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner,
            const scoped_refptr<base::SingleThreadTaskRunner>& file_task_runner)
            override
        {
            task_runner_ = glib_task_runner;
            return true;
        }

        void ShutDown() override { }

        bool SetUpNotifications(
            ProxyConfigServiceLinux::Delegate* delegate) override
        {
            return true;
        }

        const scoped_refptr<base::SingleThreadTaskRunner>& GetNotificationTaskRunner()
            override
        {
            return task_runner_;
        }

        ProxyConfigSource GetConfigSource() override
        {
            return PROXY_CONFIG_SOURCE_TEST;
        }

        bool GetString(StringSetting key, std::string* result) override
        {
            const char* value = strings_table.Get(key);
            if (value) {
                *result = value;
                return true;
            }
            return false;
        }

        bool GetBool(BoolSetting key, bool* result) override
        {
            BoolSettingValue value = bools_table.Get(key);
            switch (value) {
            case UNSET:
                return false;
            case TRUE:
                *result = true;
                break;
            case FALSE:
                *result = false;
            }
            return true;
        }

        bool GetInt(IntSetting key, int* result) override
        {
            // We don't bother to distinguish unset keys from 0 values.
            *result = ints_table.Get(key);
            return true;
        }

        bool GetStringList(StringListSetting key,
            std::vector<std::string>* result) override
        {
            *result = string_lists_table.Get(key);
            // We don't bother to distinguish unset keys from empty lists.
            return !result->empty();
        }

        bool BypassListIsReversed() override { return false; }

        bool MatchHostsUsingSuffixMatching() override { return false; }

        // Intentionally public, for convenience when setting up a test.
        GConfValues values;

    private:
        scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
        SettingsTable<StringSetting, const char*> strings_table;
        SettingsTable<BoolSetting, BoolSettingValue> bools_table;
        SettingsTable<IntSetting, int> ints_table;
        SettingsTable<StringListSetting,
            std::vector<std::string>>
            string_lists_table;
    };

    // This helper class runs ProxyConfigServiceLinux::GetLatestProxyConfig() on
    // the IO thread and synchronously waits for the result.
    // Some code duplicated from proxy_script_fetcher_unittest.cc.
    class SynchConfigGetter {
    public:
        // Takes ownership of |config_service|.
        explicit SynchConfigGetter(ProxyConfigServiceLinux* config_service)
            : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                base::WaitableEvent::InitialState::NOT_SIGNALED)
            , io_thread_("IO_Thread")
            , config_service_(config_service)
        {
            // Start an IO thread.
            base::Thread::Options options;
            options.message_loop_type = base::MessageLoop::TYPE_IO;
            io_thread_.StartWithOptions(options);

            // Make sure the thread started.
            io_thread_.task_runner()->PostTask(
                FROM_HERE,
                base::Bind(&SynchConfigGetter::Init, base::Unretained(this)));
            Wait();
        }

        ~SynchConfigGetter()
        {
            // Let the config service post a destroy message to the IO thread
            // before cleaning up that thread.
            delete config_service_;
            // Clean up the IO thread.
            io_thread_.task_runner()->PostTask(
                FROM_HERE,
                base::Bind(&SynchConfigGetter::CleanUp, base::Unretained(this)));
            Wait();
        }

        // Does gconf setup and initial fetch of the proxy config,
        // all on the calling thread (meant to be the thread with the
        // default glib main loop, which is the UI thread).
        void SetupAndInitialFetch()
        {
            // We pass the mock IO thread as both the IO and file threads.
            config_service_->SetupAndFetchInitialConfig(
                base::ThreadTaskRunnerHandle::Get(), io_thread_.task_runner(),
                io_thread_.task_runner());
        }
        // Synchronously gets the proxy config.
        ProxyConfigService::ConfigAvailability SyncGetLatestProxyConfig(
            ProxyConfig* config)
        {
            io_thread_.task_runner()->PostTask(
                FROM_HERE, base::Bind(&SynchConfigGetter::GetLatestConfigOnIOThread, base::Unretained(this)));
            Wait();
            *config = proxy_config_;
            return get_latest_config_result_;
        }

    private:
        // [Runs on |io_thread_|]
        void Init()
        {
            event_.Signal();
        }

        // Calls GetLatestProxyConfig, running on |io_thread_| Signals |event_|
        // on completion.
        void GetLatestConfigOnIOThread()
        {
            get_latest_config_result_ = config_service_->GetLatestProxyConfig(&proxy_config_);
            event_.Signal();
        }

        // [Runs on |io_thread_|] Signals |event_| on cleanup completion.
        void CleanUp()
        {
            base::RunLoop().RunUntilIdle();
            event_.Signal();
        }

        void Wait()
        {
            event_.Wait();
            event_.Reset();
        }

        base::WaitableEvent event_;
        base::Thread io_thread_;

        ProxyConfigServiceLinux* config_service_;

        // The config obtained by |io_thread_| and read back by the main
        // thread.
        ProxyConfig proxy_config_;

        // Return value from GetLatestProxyConfig().
        ProxyConfigService::ConfigAvailability get_latest_config_result_;
    };

    // This test fixture is only really needed for the KDEConfigParser test case,
    // but all the test cases with the same prefix ("ProxyConfigServiceLinuxTest")
    // must use the same test fixture class (also "ProxyConfigServiceLinuxTest").
    class ProxyConfigServiceLinuxTest : public PlatformTest {
    protected:
        void SetUp() override
        {
            PlatformTest::SetUp();
            // Set up a temporary KDE home directory.
            std::string prefix("ProxyConfigServiceLinuxTest_user_home");
            base::CreateNewTempDirectory(prefix, &user_home_);
            config_home_ = user_home_.Append(FILE_PATH_LITERAL(".config"));
            kde_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde"));
            base::FilePath path = kde_home_.Append(FILE_PATH_LITERAL("share"));
            path = path.Append(FILE_PATH_LITERAL("config"));
            base::CreateDirectory(path);
            kioslaverc_ = path.Append(FILE_PATH_LITERAL("kioslaverc"));
            // Set up paths but do not create the directory for .kde4.
            kde4_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde4"));
            path = kde4_home_.Append(FILE_PATH_LITERAL("share"));
            kde4_config_ = path.Append(FILE_PATH_LITERAL("config"));
            kioslaverc4_ = kde4_config_.Append(FILE_PATH_LITERAL("kioslaverc"));
            // Set up paths for KDE 5
            kioslaverc5_ = config_home_.Append(FILE_PATH_LITERAL("kioslaverc"));
        }

        void TearDown() override
        {
            // Delete the temporary KDE home directory.
            base::DeleteFile(user_home_, true);
            PlatformTest::TearDown();
        }

        base::FilePath user_home_;
        base::FilePath config_home_;
        // KDE3 paths.
        base::FilePath kde_home_;
        base::FilePath kioslaverc_;
        // KDE4 paths.
        base::FilePath kde4_home_;
        base::FilePath kde4_config_;
        base::FilePath kioslaverc4_;
        // KDE5 paths.
        base::FilePath kioslaverc5_;
    };

// Builds an identifier for each test in an array.
#define TEST_DESC(desc) base::StringPrintf("at line %d <%s>", __LINE__, desc)

    TEST_F(ProxyConfigServiceLinuxTest, BasicGConfTest)
    {
        std::vector<std::string> empty_ignores;

        std::vector<std::string> google_ignores;
        google_ignores.push_back("*.google.com");

        // Inspired from proxy_config_service_win_unittest.cc.
        // Very neat, but harder to track down failures though.
        const struct {
            // Short description to identify the test
            std::string description;

            // Input.
            GConfValues values;

            // Expected outputs (availability and fields of ProxyConfig).
            ProxyConfigService::ConfigAvailability availability;
            bool auto_detect;
            GURL pac_url;
            ProxyRulesExpectation proxy_rules;
        } tests[] = {
            {
                TEST_DESC("No proxying"),
                {
                    // Input.
                    "none", // mode
                    "", // autoconfig_url
                    "", "", "", "", // hosts
                    0, 0, 0, 0, // ports
                    FALSE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Auto detect"),
                {
                    // Input.
                    "auto", // mode
                    "", // autoconfig_url
                    "", "", "", "", // hosts
                    0, 0, 0, 0, // ports
                    FALSE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                true, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Valid PAC URL"),
                {
                    // Input.
                    "auto", // mode
                    "http://wpad/wpad.dat", // autoconfig_url
                    "", "", "", "", // hosts
                    0, 0, 0, 0, // ports
                    FALSE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL("http://wpad/wpad.dat"), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Invalid PAC URL"),
                {
                    // Input.
                    "auto", // mode
                    "wpad.dat", // autoconfig_url
                    "", "", "", "", // hosts
                    0, 0, 0, 0, // ports
                    FALSE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Single-host in proxy list"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", "", "", "", // hosts
                    80, 0, 0, 0, // ports
                    TRUE, TRUE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "www.google.com:80", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("use_http_proxy is honored"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", "", "", "", // hosts
                    80, 0, 0, 0, // ports
                    FALSE, TRUE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("use_http_proxy and use_same_proxy are optional"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", "", "", "", // hosts
                    80, 0, 0, 0, // ports
                    UNSET, UNSET, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme(
                    "www.google.com:80", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Single-host, different port"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", "", "", "", // hosts
                    88, 0, 0, 0, // ports
                    TRUE, TRUE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "www.google.com:88", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("Per-scheme proxy rules"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", // http_host
                    "www.foo.com", // secure_host
                    "ftp.foo.com", // ftp
                    "", // socks
                    88, 110, 121, 0, // ports
                    TRUE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme(
                    "www.google.com:88", // http
                    "www.foo.com:110", // https
                    "ftp.foo.com:121", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("socks"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "", "", "", "socks.com", // hosts
                    0, 0, 0, 99, // ports
                    TRUE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "socks5://socks.com:99", // single proxy
                    "") // bypass rules
            },

            {
                TEST_DESC("Per-scheme proxy rules with fallback to SOCKS"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", // http_host
                    "www.foo.com", // secure_host
                    "ftp.foo.com", // ftp
                    "foobar.net", // socks
                    88, 110, 121, 99, // ports
                    TRUE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerSchemeWithSocks(
                    "www.google.com:88", // http
                    "www.foo.com:110", // https
                    "ftp.foo.com:121", // ftp
                    "socks5://foobar.net:99", // socks
                    ""), // bypass rules
            },

            {
                TEST_DESC("Per-scheme proxy rules (just HTTP) with fallback to SOCKS"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", // http_host
                    "", // secure_host
                    "", // ftp
                    "foobar.net", // socks
                    88, 0, 0, 99, // ports
                    TRUE, FALSE, FALSE, // use, same, auth
                    empty_ignores, // ignore_hosts
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerSchemeWithSocks(
                    "www.google.com:88", // http
                    "", // https
                    "", // ftp
                    "socks5://foobar.net:99", // socks
                    ""), // bypass rules
            },

            {
                TEST_DESC("Bypass *.google.com"),
                {
                    // Input.
                    "manual", // mode
                    "", // autoconfig_url
                    "www.google.com", "", "", "", // hosts
                    80, 0, 0, 0, // ports
                    TRUE, TRUE, FALSE, // use, same, auth
                    google_ignores, // ignore_hosts
                },

                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "www.google.com:80", // single proxy
                    "*.google.com"), // bypass rules
            },
        };

        for (size_t i = 0; i < arraysize(tests); ++i) {
            SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i,
                tests[i].description.c_str()));
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            MockSettingGetter* setting_getter = new MockSettingGetter;
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env), setting_getter));
            ProxyConfig config;
            setting_getter->values = tests[i].values;
            sync_config_getter.SetupAndInitialFetch();
            ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config);
            EXPECT_EQ(tests[i].availability, availability);

            if (availability == ProxyConfigService::CONFIG_VALID) {
                EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
                EXPECT_EQ(tests[i].pac_url, config.pac_url());
                EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
            }
        }
    }

    TEST_F(ProxyConfigServiceLinuxTest, BasicEnvTest)
    {
        // Inspired from proxy_config_service_win_unittest.cc.
        const struct {
            // Short description to identify the test
            std::string description;

            // Input.
            EnvVarValues values;

            // Expected outputs (availability and fields of ProxyConfig).
            ProxyConfigService::ConfigAvailability availability;
            bool auto_detect;
            GURL pac_url;
            ProxyRulesExpectation proxy_rules;
        } tests[] = {
            {
                TEST_DESC("No proxying"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    nullptr, // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    "*", // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Auto detect"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    "", // auto_proxy
                    nullptr, // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                true, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Valid PAC URL"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    "http://wpad/wpad.dat", // auto_proxy
                    nullptr, // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL("http://wpad/wpad.dat"), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Invalid PAC URL"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    "wpad.dat", // auto_proxy
                    nullptr, // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Single-host in proxy list"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "www.google.com", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single("www.google.com:80", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("Single-host, different port"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "www.google.com:99", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single("www.google.com:99", // single
                    ""), // bypass rules
            },

            {
                TEST_DESC("Tolerate a scheme"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "http://www.google.com:99", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single("www.google.com:99", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("Per-scheme proxy rules"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    nullptr, // all_proxy
                    "www.google.com:80", "www.foo.com:110",
                    "ftp.foo.com:121", // per-proto
                    nullptr, nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "www.foo.com:110", // https
                    "ftp.foo.com:121", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("socks"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    "socks.com:888", nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "socks5://socks.com:888", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("socks4"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    "socks.com:888", "4", // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "socks4://socks.com:888", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("socks default port"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto proxies
                    "socks.com", nullptr, // SOCKS
                    nullptr, // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "socks5://socks.com:1080", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("bypass"),
                {
                    // Input.
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    "www.google.com", // all_proxy
                    nullptr, nullptr, nullptr, // per-proto
                    nullptr, nullptr, // SOCKS
                    ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8", // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "www.google.com:80",
                    "*.google.com,*foo.com:99,1.2.3.4:22,127.0.0.1/8"),
            },
        };

        for (size_t i = 0; i < arraysize(tests); ++i) {
            SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i,
                tests[i].description.c_str()));
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values = tests[i].values;
            MockSettingGetter* setting_getter = new MockSettingGetter;
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env), setting_getter));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config);
            EXPECT_EQ(tests[i].availability, availability);

            if (availability == ProxyConfigService::CONFIG_VALID) {
                EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
                EXPECT_EQ(tests[i].pac_url, config.pac_url());
                EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
            }
        }
    }

    TEST_F(ProxyConfigServiceLinuxTest, GconfNotification)
    {
        std::unique_ptr<MockEnvironment> env(new MockEnvironment);
        MockSettingGetter* setting_getter = new MockSettingGetter;
        ProxyConfigServiceLinux* service = new ProxyConfigServiceLinux(std::move(env), setting_getter);
        SynchConfigGetter sync_config_getter(service);
        ProxyConfig config;

        // Start with no proxy.
        setting_getter->values.mode = "none";
        sync_config_getter.SetupAndInitialFetch();
        EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
            sync_config_getter.SyncGetLatestProxyConfig(&config));
        EXPECT_FALSE(config.auto_detect());

        // Now set to auto-detect.
        setting_getter->values.mode = "auto";
        // Simulate setting change notification callback.
        service->OnCheckProxyConfigSettings();
        EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
            sync_config_getter.SyncGetLatestProxyConfig(&config));
        EXPECT_TRUE(config.auto_detect());
    }

    TEST_F(ProxyConfigServiceLinuxTest, KDEConfigParser)
    {
        // One of the tests below needs a worst-case long line prefix. We build it
        // programmatically so that it will always be the right size.
        std::string long_line;
        size_t limit = ProxyConfigServiceLinux::SettingGetter::BUFFER_SIZE - 1;
        for (size_t i = 0; i < limit; ++i)
            long_line += "-";

        // Inspired from proxy_config_service_win_unittest.cc.
        const struct {
            // Short description to identify the test
            std::string description;

            // Input.
            std::string kioslaverc;
            EnvVarValues env_values;

            // Expected outputs (availability and fields of ProxyConfig).
            ProxyConfigService::ConfigAvailability availability;
            bool auto_detect;
            GURL pac_url;
            ProxyRulesExpectation proxy_rules;
        } tests[] = {
            {
                TEST_DESC("No proxying"),

                // Input.
                "[Proxy Settings]\nProxyType=0\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Auto detect"),

                // Input.
                "[Proxy Settings]\nProxyType=3\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                true, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Valid PAC URL"),

                // Input.
                "[Proxy Settings]\nProxyType=2\n"
                "Proxy Config Script=http://wpad/wpad.dat\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL("http://wpad/wpad.dat"), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Valid PAC file without file://"),

                // Input.
                "[Proxy Settings]\nProxyType=2\n"
                "Proxy Config Script=/wpad/wpad.dat\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL("file:///wpad/wpad.dat"), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Per-scheme proxy rules"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "httpsProxy=www.foo.com\nftpProxy=ftp.foo.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "www.foo.com:80", // https
                    "ftp.foo.com:80", // http
                    ""), // bypass rules
            },

            {
                TEST_DESC("Only HTTP proxy specified"),

                // Input.
                "[Proxy Settings]\nProxyType=1\n"
                "httpProxy=www.google.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Only HTTP proxy specified, different port"),

                // Input.
                "[Proxy Settings]\nProxyType=1\n"
                "httpProxy=www.google.com:88\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:88", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC(
                    "Only HTTP proxy specified, different port, space-delimited"),

                // Input.
                "[Proxy Settings]\nProxyType=1\n"
                "httpProxy=www.google.com 88\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:88", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Bypass *.google.com"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "NoProxyFor=.google.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "", // ftp
                    "*.google.com"), // bypass rules
            },

            {
                TEST_DESC("Bypass *.google.com and *.kde.org"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "NoProxyFor=.google.com,.kde.org\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme(
                    "www.google.com:80", // http
                    "", // https
                    "", // ftp
                    "*.google.com,*.kde.org"), // bypass rules
            },

            {
                TEST_DESC("Correctly parse bypass list with ReversedException"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "NoProxyFor=.google.com\nReversedException=true\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerSchemeWithBypassReversed(
                    "www.google.com:80", // http
                    "", // https
                    "", // ftp
                    "*.google.com"), // bypass rules
            },

            {
                TEST_DESC("socks"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nsocksProxy=socks.com 888\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "socks5://socks.com:888", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("socks4"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nsocksProxy=socks4://socks.com 888\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Single(
                    "socks4://socks.com:888", // single proxy
                    ""), // bypass rules
            },

            {
                TEST_DESC("Treat all hostname patterns as wildcard patterns"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "NoProxyFor=google.com,kde.org,<local>\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme(
                    "www.google.com:80", // http
                    "", // https
                    "", // ftp
                    "*google.com,*kde.org,<local>"), // bypass rules
            },

            {
                TEST_DESC("Allow trailing whitespace after boolean value"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "NoProxyFor=.google.com\nReversedException=true  \n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerSchemeWithBypassReversed(
                    "www.google.com:80", // http
                    "", // https
                    "", // ftp
                    "*.google.com"), // bypass rules
            },

            {
                TEST_DESC("Ignore settings outside [Proxy Settings]"),

                // Input.
                "httpsProxy=www.foo.com\n[Proxy Settings]\nProxyType=1\n"
                "httpProxy=www.google.com\n[Other Section]\nftpProxy=ftp.foo.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Handle CRLF line endings"),

                // Input.
                "[Proxy Settings]\r\nProxyType=1\r\nhttpProxy=www.google.com\r\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Handle blank lines and mixed line endings"),

                // Input.
                "[Proxy Settings]\r\n\nProxyType=1\n\r\nhttpProxy=www.google.com\n\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Handle localized settings"),

                // Input.
                "[Proxy Settings]\nProxyType[$e]=1\nhttpProxy[$e]=www.google.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Ignore malformed localized settings"),

                // Input.
                "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
                "httpsProxy$e]=www.foo.com\nftpProxy=ftp.foo.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "ftp.foo.com:80", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Handle strange whitespace"),

                // Input.
                "[Proxy Settings]\nProxyType [$e] =2\n"
                "  Proxy Config Script =  http:// foo\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL("http:// foo"), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Ignore all of a line which is too long"),

                // Input.
                std::string("[Proxy Settings]\nProxyType=1\nftpProxy=ftp.foo.com\n") + long_line + "httpsProxy=www.foo.com\nhttpProxy=www.google.com\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme("www.google.com:80", // http
                    "", // https
                    "ftp.foo.com:80", // ftp
                    ""), // bypass rules
            },

            {
                TEST_DESC("Indirect Proxy - no env vars set"),

                // Input.
                "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n"
                "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n",
                {}, // env_values

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::Empty(),
            },

            {
                TEST_DESC("Indirect Proxy - with env vars set"),

                // Input.
                "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n"
                "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n",
                {
                    // env_values
                    nullptr, // DESKTOP_SESSION
                    nullptr, // HOME
                    nullptr, // KDEHOME
                    nullptr, // KDE_SESSION_VERSION
                    nullptr, // XDG_CURRENT_DESKTOP
                    nullptr, // auto_proxy
                    nullptr, // all_proxy
                    "www.normal.com", // http_proxy
                    "www.secure.com", // https_proxy
                    "ftp.foo.com", // ftp_proxy
                    nullptr, nullptr, // SOCKS
                    ".google.com, .kde.org", // no_proxy
                },

                // Expected result.
                ProxyConfigService::CONFIG_VALID,
                false, // auto_detect
                GURL(), // pac_url
                ProxyRulesExpectation::PerScheme(
                    "www.normal.com:80", // http
                    "www.secure.com:80", // https
                    "ftp.foo.com:80", // ftp
                    "*.google.com,*.kde.org"), // bypass rules
            },
        };

        for (size_t i = 0; i < arraysize(tests); ++i) {
            SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i,
                tests[i].description.c_str()));
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values = tests[i].env_values;
            // Force the KDE getter to be used and tell it where the test is.
            env->values.DESKTOP_SESSION = "kde4";
            env->values.KDEHOME = kde_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            // Overwrite the kioslaverc file.
            base::WriteFile(kioslaverc_, tests[i].kioslaverc.c_str(),
                tests[i].kioslaverc.length());
            sync_config_getter.SetupAndInitialFetch();
            ProxyConfigService::ConfigAvailability availability = sync_config_getter.SyncGetLatestProxyConfig(&config);
            EXPECT_EQ(tests[i].availability, availability);

            if (availability == ProxyConfigService::CONFIG_VALID) {
                EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
                EXPECT_EQ(tests[i].pac_url, config.pac_url());
                EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
            }
        }
    }

    TEST_F(ProxyConfigServiceLinuxTest, KDEHomePicker)
    {
        // Auto detect proxy settings.
        std::string slaverc3 = "[Proxy Settings]\nProxyType=3\n";
        // Valid PAC URL.
        std::string slaverc4 = "[Proxy Settings]\nProxyType=2\n"
                               "Proxy Config Script=http://wpad/wpad.dat\n";
        GURL slaverc4_pac_url("http://wpad/wpad.dat");
        // Basic HTTP proxy setting.
        std::string slaverc5 = "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com 80\n";
        ProxyRulesExpectation slaverc5_rules = ProxyRulesExpectation::PerScheme("www.google.com:80", // http
            "", // https
            "", // ftp
            ""); // bypass rules

        // Overwrite the .kde kioslaverc file.
        base::WriteFile(kioslaverc_, slaverc3.c_str(), slaverc3.length());

        // If .kde4 exists it will mess up the first test. It should not, as
        // we created the directory for $HOME in the test setup.
        CHECK(!base::DirectoryExists(kde4_home_));

        {
            SCOPED_TRACE("KDE4, no .kde4 directory, verify fallback");
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values.DESKTOP_SESSION = "kde4";
            env->values.HOME = user_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
                sync_config_getter.SyncGetLatestProxyConfig(&config));
            EXPECT_TRUE(config.auto_detect());
            EXPECT_EQ(GURL(), config.pac_url());
        }

        // Now create .kde4 and put a kioslaverc in the config directory.
        // Note that its timestamp will be at least as new as the .kde one.
        base::CreateDirectory(kde4_config_);
        base::WriteFile(kioslaverc4_, slaverc4.c_str(), slaverc4.length());
        CHECK(base::PathExists(kioslaverc4_));

        {
            SCOPED_TRACE("KDE4, .kde4 directory present, use it");
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values.DESKTOP_SESSION = "kde4";
            env->values.HOME = user_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
                sync_config_getter.SyncGetLatestProxyConfig(&config));
            EXPECT_FALSE(config.auto_detect());
            EXPECT_EQ(slaverc4_pac_url, config.pac_url());
        }

        {
            SCOPED_TRACE("KDE3, .kde4 directory present, ignore it");
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values.DESKTOP_SESSION = "kde";
            env->values.HOME = user_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
                sync_config_getter.SyncGetLatestProxyConfig(&config));
            EXPECT_TRUE(config.auto_detect());
            EXPECT_EQ(GURL(), config.pac_url());
        }

        {
            SCOPED_TRACE("KDE4, .kde4 directory present, KDEHOME set to .kde");
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values.DESKTOP_SESSION = "kde4";
            env->values.HOME = user_home_.value().c_str();
            env->values.KDEHOME = kde_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
                sync_config_getter.SyncGetLatestProxyConfig(&config));
            EXPECT_TRUE(config.auto_detect());
            EXPECT_EQ(GURL(), config.pac_url());
        }

        // Finally, make the .kde4 config directory older than the .kde directory
        // and make sure we then use .kde instead of .kde4 since it's newer.
        base::TouchFile(kde4_config_, base::Time(), base::Time());

        {
            SCOPED_TRACE("KDE4, very old .kde4 directory present, use .kde");
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values.DESKTOP_SESSION = "kde4";
            env->values.HOME = user_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
                sync_config_getter.SyncGetLatestProxyConfig(&config));
            EXPECT_TRUE(config.auto_detect());
            EXPECT_EQ(GURL(), config.pac_url());
        }

        // For KDE 5 create ${HOME}/.config and put a kioslaverc in the directory.
        base::CreateDirectory(config_home_);
        base::WriteFile(kioslaverc5_, slaverc5.c_str(), slaverc5.length());
        CHECK(base::PathExists(kioslaverc5_));

        {
            SCOPED_TRACE("KDE5, .kde and .kde4 present, use .config");
            std::unique_ptr<MockEnvironment> env(new MockEnvironment);
            env->values.XDG_CURRENT_DESKTOP = "KDE";
            env->values.KDE_SESSION_VERSION = "5";
            env->values.HOME = user_home_.value().c_str();
            SynchConfigGetter sync_config_getter(
                new ProxyConfigServiceLinux(std::move(env)));
            ProxyConfig config;
            sync_config_getter.SetupAndInitialFetch();
            EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
                sync_config_getter.SyncGetLatestProxyConfig(&config));
            EXPECT_FALSE(config.auto_detect());
            EXPECT_TRUE(slaverc5_rules.Matches(config.proxy_rules()));
        }
    }

} // namespace

} // namespace net
